package org.apache.lucene.search.spell.suggest; // need follow the named  rule. use lower-case

import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.spell.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MMapDirectory;
import org.apache.lucene.tests.analysis.MockAnalyzer;
import org.apache.lucene.tests.util.English;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.util.AssertionErrors.assertFalse;

@Slf4j
public class SpellCheckerDemo {
    private SpellChecker spellChecker;
    private Directory userindex, spellindex;
    private Analyzer analyzer;
    private List<IndexSearcher> searchers;
    private IndexWriter w =null;
    private Random random = null;
    IndexWriter writer =null;

    @BeforeEach
    public void setUp() throws Exception {

        // create a user index
        String uri_spell = "F:\\integration\\search\\lucene-data\\spell";
        String uri_user = "F:\\integration\\search\\lucene-data\\user";

        try {
            userindex = new MMapDirectory(Paths.get(uri_user));
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig();
            indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
            spellindex = new MMapDirectory(Paths.get(uri_spell));

            random = new Random();
        } catch (IOException e) {
            throw new RuntimeException("mmap failed");
        }

        analyzer =  new WhitespaceAnalyzer(30);
        writer = new IndexWriter(userindex, new IndexWriterConfig(analyzer));
    }

    @AfterEach
    public void tearDown() throws Exception {
        userindex.close();
        spellChecker.close();
        spellindex.close();
        analyzer.close();
    }
    @Test
    public void index() throws IOException {
        for (int i = 0; i < 1000; i++) {
            Document doc = new Document();
            doc.add(new TextField("field1", English.intToEnglish(i), Field.Store.YES));
            doc.add(
                    new TextField("field2", English.intToEnglish(i + 1), Field.Store.YES)); // + word thousand
            doc.add(
                    new TextField(
                            "field3", "fvei" + (i % 2 == 0 ? " five" : ""), Field.Store.YES)); // + word thousand
            writer.addDocument(doc);
        }
        {
            Document doc = new Document();
            doc.add(new TextField("field1", "eight", Field.Store.YES)); // "eight" in
            // the index
            // twice
            writer.addDocument(doc);
        }
        {
            Document doc = new Document();
            doc.add(
                    new TextField(
                            "field1",
                            "twenty-one twenty-one",
                            Field.Store.YES)); // "twenty-one" in the index thrice
            writer.addDocument(doc);
        }
        {
            Document doc = new Document();
            doc.add(new TextField("field1", "twenty", Field.Store.YES)); // "twenty"
            // in the
            // index
            // twice
            writer.addDocument(doc);
        }

        writer.close();

    }
    @Test
    public void testSuggest() throws IOException {

        IndexReader r = DirectoryReader.open(userindex);

        spellChecker = new SpellChecker(spellindex);
        spellChecker.clearIndex();
        // userIndex the field 'field1' , index the field 'word' to the spellindex but the value is a no-position terms
        addwords(r, spellChecker, "field1");
        int num_field1 = this.numdoc();

        addwords(r, spellChecker, "field2");
        int num_field2 = this.numdoc();

        assertEquals(num_field2, num_field1 + 1);

        checkCommonSuggestions(r);
        checkLevenshteinSuggestions(r);

        spellChecker.setStringDistance(new JaroWinklerDistance());
        spellChecker.setAccuracy(0.8f);
        checkCommonSuggestions(r);
        checkJaroWinklerSuggestions();
        // the accuracy is set to 0.8 by default, but the best result has a score of 0.925
        String[] similar = spellChecker.suggestSimilar("fvie", 2, 0.93f);
        assertTrue(similar.length == 0);
        similar = spellChecker.suggestSimilar("fvie", 2, 0.92f);
        assertTrue(similar.length == 1);

        similar = spellChecker.suggestSimilar("fiv", 2);
        assertTrue(similar.length > 0);
        assertEquals(similar[0], "five");

        spellChecker.setStringDistance(new NGramDistance(2));
        spellChecker.setAccuracy(0.5f);
        checkCommonSuggestions(r);
        checkNGramSuggestions();

        r.close();
    }
    public void testSuggestModes() throws Exception {
        IndexReader r = DirectoryReader.open(userindex);
        spellChecker.clearIndex();
        addwords(r, spellChecker, "field1");

        {
            String[] similar =
                    spellChecker.suggestSimilar("", 2, r, "field1", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
            assertEquals(0, similar.length);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar(
                            "eighty", 2, r, "field1", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
            assertEquals(1, similar.length);
            assertEquals("eighty", similar[0]);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar(
                            "eight", 2, r, "field1", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
            assertEquals(1, similar.length);
            assertEquals("eight", similar[0]);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("", 5, r, "field1", SuggestMode.SUGGEST_MORE_POPULAR);
            assertEquals(0, similar.length);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("eighty", 5, r, "field1", SuggestMode.SUGGEST_MORE_POPULAR);
            assertEquals(5, similar.length);
            assertEquals("eight", similar[0]);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("twenty", 5, r, "field1", SuggestMode.SUGGEST_MORE_POPULAR);
            assertEquals(1, similar.length);
            assertEquals("twenty-one", similar[0]);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("eight", 5, r, "field1", SuggestMode.SUGGEST_MORE_POPULAR);
            assertEquals(0, similar.length);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("", 5, r, "field1", SuggestMode.SUGGEST_ALWAYS);
            assertEquals(0, similar.length);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("eighty", 5, r, "field1", SuggestMode.SUGGEST_ALWAYS);
            assertEquals(5, similar.length);
            assertEquals("eight", similar[0]);
        }

        {
            String[] similar =
                    spellChecker.suggestSimilar("eight", 5, r, "field1", SuggestMode.SUGGEST_ALWAYS);
            assertEquals(5, similar.length);
            assertEquals("eighty", similar[0]);
        }
        r.close();
    }

    private void checkLevenshteinSuggestions(IndexReader r) throws IOException {
        // test small word
        String[] similar = spellChecker.suggestSimilar("fvie", 2);
        assertEquals(1, similar.length);
        assertEquals(similar[0], "five");

        similar = spellChecker.suggestSimilar("five", 2);
        assertEquals(1, similar.length);
        assertEquals(similar[0], "nine"); // don't suggest a word for itself

        similar = spellChecker.suggestSimilar("fiv", 2);
        assertEquals(1, similar.length);
        assertEquals(similar[0], "five");

        similar = spellChecker.suggestSimilar("ive", 2);
        assertEquals(2, similar.length);
        assertEquals(similar[0], "five");
        assertEquals(similar[1], "nine");

        similar = spellChecker.suggestSimilar("fives", 2);
        assertEquals(1, similar.length);
        assertEquals(similar[0], "five");

        similar = spellChecker.suggestSimilar("fie", 2);
        assertEquals(2, similar.length);
        assertEquals(similar[0], "five");
        assertEquals(similar[1], "nine");

        similar = spellChecker.suggestSimilar("fi", 2);
        assertEquals(1, similar.length);
        assertEquals(similar[0], "five");

        // test restraint to a field
        similar =
                spellChecker.suggestSimilar(
                        "tousand", 10, r, "field1", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
        assertEquals(0, similar.length); // there isn't the term thousand in the field field1

        similar =
                spellChecker.suggestSimilar(
                        "tousand", 10, r, "field2", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
        assertEquals(1, similar.length); // there is the term thousand in the field field2

        similar = spellChecker.suggestSimilar("onety", 2);
        assertEquals(2, similar.length);
        assertEquals(similar[0], "ninety");
        assertEquals(similar[1], "one");
        // should not throw exception
        spellChecker.suggestSimilar("tousand", 10, r, null, SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
    }
    private void checkNGramSuggestions() throws IOException {
        String[] similar = spellChecker.suggestSimilar("onety", 2);
        assertEquals(2, similar.length);
        assertEquals(similar[0], "one");
        assertEquals(similar[1], "ninety");
    }
    private void checkJaroWinklerSuggestions() throws IOException {
        String[] similar = spellChecker.suggestSimilar("onety", 2);
        assertEquals(2, similar.length);
        assertEquals(similar[0], "one");
        assertEquals(similar[1], "ninety");
    }
    private void checkCommonSuggestions(IndexReader r) throws IOException {
        String[] similar = spellChecker.suggestSimilar("fvie", 2);
        assertTrue(similar.length > 0);
        assertEquals(similar[0], "five");

        similar = spellChecker.suggestSimilar("five", 2);
        if (similar.length > 0) {
            Assertions.assertFalse(similar[0].equals("five")); // don't suggest a word for itself
        }

        similar = spellChecker.suggestSimilar("fiv", 2);
        assertTrue(similar.length > 0);
        assertEquals(similar[0], "five");

        similar = spellChecker.suggestSimilar("fives", 2);
        assertTrue(similar.length > 0);
        assertEquals(similar[0], "five");

        assertTrue(similar.length > 0);
        similar = spellChecker.suggestSimilar("fie", 2);
        assertEquals(similar[0], "five");

        //  test restraint to a field
        similar =
                spellChecker.suggestSimilar(
                        "tousand", 10, r, "field1", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
        assertEquals(0, similar.length); // there isn't the term thousand in the field field1

        similar =
                spellChecker.suggestSimilar(
                        "tousand", 10, r, "field2", SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX);
        assertEquals(1, similar.length); // there is the term thousand in the field field2
    }
    private void addwords(IndexReader r, SpellChecker sc, String field) throws IOException {
        sc.indexDictionary(new LuceneDictionary(r, field),new IndexWriterConfig(analyzer), false);
    }
    private int numdoc() throws IOException {
        IndexReader rs = DirectoryReader.open(spellindex);
        int num = rs.numDocs();
        assertTrue(num != 0);
        // System.out.println("num docs: " + num);
        rs.close();
        return num;
    }

}
